mod generate;
mod utils;
mod extract;

use convert_case::{Case, Casing};
use extract::*;
use generate::*;

use quote::{format_ident, quote};
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn get(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let aqua = utils::get_aqua_path();
    route(quote! { #aqua::http::HttpMethod::Get }, attr, item)
}

#[proc_macro_attribute]
pub fn post(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let aqua = utils::get_aqua_path();
    route(quote! { #aqua::http::HttpMethod::Post }, attr, item)
}

#[proc_macro_attribute]
pub fn put(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let aqua = utils::get_aqua_path();
    route(quote! { #aqua::http::HttpMethod::Put }, attr, item)
}

#[proc_macro_attribute]
pub fn patch(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let aqua = utils::get_aqua_path();
    route(quote! { #aqua::http::HttpMethod::Patch }, attr, item)
}

#[proc_macro]
pub fn router(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let func_ident = parse_macro_input!(input as syn::Ident);
    let struct_name = format!("{}Router", func_ident.to_string().to_case(Case::UpperCamel));
    let struct_ident = syn::Ident::new(&struct_name, func_ident.span());

    let expanded = quote! {
        #struct_ident::new()
    };

    proc_macro::TokenStream::from(expanded)
}

fn route(method: proc_macro2::TokenStream, attr: proc_macro::TokenStream, item: proc_macro::TokenStream)  -> proc_macro::TokenStream {
    let aqua = utils::get_aqua_path();
    
    // Parse the attr!
    let route_attr = parse_macro_input!(attr as RouteAttr);
    let use_body = route_attr.body_param.is_some();

    // Parse handle
    let handle_fn = parse_macro_input!(item as ItemFn);
    let handle_name = &handle_fn.sig.ident;
    let param_info = match ParamInfo::extracted(&handle_fn, &route_attr) {
        Ok(v) => v,
        Err(err) => return err.into(),
    };    

    let struct_name = route_attr.struct_name.unwrap_or(
        format_ident!("{}Router", &handle_name.to_string().to_case(Case::UpperCamel))
    );

    // Generate code
    let path_args_transform_code = generate_path_args_transform_code(&param_info.path_params);
    let query_args_transform_code = generate_query_args_transform_code(&param_info.query_params);
    let body_arg_transform_code = generate_body_args_transform_code(param_info.body_param.as_ref());
    let state_arg_transform_code = generate_state_args_transform_code(param_info.state_param.as_ref());
    let return_code = generate_return_code(&handle_fn, &struct_name, &param_info.all_params_names);
    let segment_code = generate_segements_code(&route_attr.path);

    let code = quote! {
        pub struct #struct_name {
            info: #aqua::server::HttpRouterInfo,
        }
        
        impl #struct_name {
            pub fn new() -> Self {
                #segment_code
                
                Self {
                    info: #aqua::server::HttpRouterInfo {
                        method: #method,
                        segments,
                        use_body: #use_body, 
                    }
                }
            }

            #handle_fn
        }

        #[async_trait::async_trait]
        impl #aqua::server::HttpRouter for #struct_name {
            fn route_info(&self) -> &#aqua::server::HttpRouterInfo {
                &self.info
            }

            async fn handle(&self, path_args: std::collections::HashMap<String, String>, query_args: &std::collections::HashMap<String, String>, body: Option<Vec<u8>>, states: &#aqua::server::HttpStateContainer) -> #aqua::http::HttpResponse {
                #(#path_args_transform_code)*
                #(#query_args_transform_code)*
                #body_arg_transform_code
                #state_arg_transform_code
                #return_code
            }
        }
    };    

    code.into()
}
